use crate::*;

use syn::{
    braced, parenthesized,
    parse::{Parse, ParseStream},
    Error as ParseError, Ident, ReturnType, Token, Type, Visibility,
};

impl Parse for ServiceMacroOption {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let generate_which_side = input.parse::<Ident>()?;
        match &*generate_which_side.to_string() {
            "both_sides" => Ok(Self {
                server_side: true,
                client_side: true,
            }),
            "client" => Ok(Self {
                server_side: false,
                client_side: true,
            }),
            "server" => Ok(Self {
                server_side: true,
                client_side: false,
            }),
            _ => Err(ParseError::new(
                input.span(),
                r#"Should be one of "both_sides", "server" and "client""#,
            )),
        }
    }
}

// vis "trait" ident "{"
//     List[RpcProcedure]
// "}"
impl Parse for RpcServiceTrait {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let vis = input.parse::<Visibility>()?;
        input.parse::<Token![trait]>()?;
        let name = input.parse::<Ident>()?;

        let within_brace;
        braced!(within_brace in input);

        let mut fns = Vec::new();
        while !within_brace.is_empty() {
            fns.push(within_brace.parse::<RpcProcedure>()?);
        }

        Ok(Self { vis, name, fns })
    }
}

// "async" "fn" ident "(" "&" "self" "," ident ":" type "," "ident" ":" "type" "," ... ")" ReturnType ";"
impl Parse for RpcProcedure {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        input.parse::<Token!(async)>()?;
        input.parse::<Token!(fn)>()?;
        let fn_name = input.parse::<Ident>()?;

        let within_paren;
        parenthesized!(within_paren in input);

        within_paren.parse::<Token![&]>()?;
        within_paren.parse::<Token![self]>()?;
        within_paren.parse::<Token![,]>()?;

        let mut args = Vec::new();
        while !within_paren.is_empty() {
            let arg_name = within_paren.parse::<Ident>()?;
            within_paren.parse::<Token![:]>()?;
            let arg_typ = within_paren.parse::<Box<Type>>()?;
            args.push(TypedIdent(arg_name, arg_typ));

            if within_paren.is_empty() {
                break;
            }
            within_paren.parse::<Token![,]>()?;
        }

        let ret_type = RetType::from(input.parse::<ReturnType>()?);
        input.parse::<Token!(;)>()?;

        Ok(Self {
            fn_name,
            args,
            ret_type,
        })
    }
}
